package com.example.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 作者:xuSen
 * 日期2025/4/13 12:57
 */
@Component
public class JwtUtils {
    @Value("${spring.security.jwt.key}")
    String key;
    @Value("${spring.security.jwt.expire}")
    int expire;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 创建一个JSON Web Token (JWT)
     *
     * @param details  用户详情，包含用户权限等信息
     * @param id       用户ID
     * @param username 用户名
     * @return 生成的JWT字符串
     * <p>
     * 此方法使用用户详情和指定的算法生成一个JWT它包含了用户ID、用户名和用户权限等信息，
     * 并设置了过期时间和签发时间使用HMAC256算法进行签名，以确保令牌的完整性和安全性
     */
    public String createJwt(UserDetails details, int id, String username) {
        // 创建HMAC256算法实例，用于签名JWT
        Algorithm algorithm = Algorithm.HMAC256(key);
        // 获取JWT的过期时间
        Date expire = this.expireTime();
        // 构建JWT
        return JWT.create()
                .withJWTId(UUID.randomUUID().toString())
                .withClaim("id", id) // 设置用户ID
                .withClaim("name", username) // 设置用户名
                .withClaim("authorities", details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList()) // 设置用户权限
                .withExpiresAt(expire) // 设置过期时间
                .withIssuedAt(new Date()) // 设置签发时间
                .sign(algorithm); // 使用算法签名JWT
    }
    /**
     * 使JWT（JSON Web Token）失效
     * 此方法用于将给定的JWT标记为失效，首先验证令牌的格式和有效性，然后尝试删除它
     */
    public boolean invalidateJwt(String headerToken) {
        // 将令牌字符串转换为标准格式
        String token = this.convertToken(headerToken);
        if (token == null)
            return false;
        // 使用HMAC256算法初始化JWT验证器
        Algorithm algorithm = Algorithm.HMAC256(key);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            // 验证JWT令牌并获取解码后的JWT对象
            DecodedJWT jwt = jwtVerifier.verify(token);
            // 获取JWT中的ID
            String id = jwt.getId();
            // 调用deleteToken方法删除令牌，参数包括ID和过期时间戳
            return this.deleteToken(id, jwt.getExpiresAt());
        }catch (JWTVerificationException e){
            // 如果令牌验证失败，返回false
            return false;
        }
    }

    /**
     * 删除用户令牌
     *
     * 将指定的令牌添加到黑名单中，以撤销其有效性
     *
     * @param uuid 用户唯一标识符，用于标识令牌
     * @param time 令牌的过期时间，用于设置黑名单中的令牌有效期
     * @return 如果删除成功返回true，否则返回false
     */
    private boolean deleteToken(String uuid, Date time) {
        // 检查令牌是否已经无效，如果无效则直接返回false
        if(this.isInvalidToken(uuid)){
            return false;
        }
        // 获取当前时间，用于计算令牌剩余的有效时间
        Date date = new Date();
        // 计算令牌剩余的有效时间，如果为负数则设为0
        long expire=Math.max(time.getTime()-date.getTime(),0);
        // 将令牌添加到黑名单中，并设置有效期，使得该令牌在剩余的有效时间内无法被使用
        stringRedisTemplate.opsForValue().set(Const.JWT_BLACK_LIST+uuid,"",expire,TimeUnit.MILLISECONDS);
        // 返回删除成功
        return true;
    }

    /**
     * 检查令牌是否无效
     * 通过检查Redis中是否存在指定的uuid键来判断令牌是否已经在黑名单中
     */
    private boolean isInvalidToken(String uuid) {
        // 判断指定的JWT是否在黑名单中
        // 参数: uuid - JWT的唯一标识符
        // 返回值: 如果JWT在黑名单中，则返回true；否则返回false
        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(Const.JWT_BLACK_LIST+uuid));
//        return stringRedisTemplate.hasKey(Const.JWT_BLACK_LIST+uuid);
    }



    /**
     * 解析JWT令牌
     * 该方法主要用于验证和解析传入的令牌字符串，以获取解码后的JWT对象
     * 如果令牌为空、验证失败或已过期，则返回null
     *
     * @param headerToken HTTP请求头中提取的令牌字符串
     * @return 解码后的JWT对象，如果令牌无效或已过期，则返回null
     */
    public DecodedJWT resoleJwt(String headerToken) {
        // 将令牌字符串转换为标准格式
        String token = this.convertToken(headerToken);
        if (token == null) {
            return null;
        }
        // 使用HMAC256算法初始化JWT验证器
        Algorithm algorithm = Algorithm.HMAC256(key);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            // 验证令牌并获取解码后的JWT对象是否合法
            DecodedJWT jwt = jwtVerifier.verify(token);
            // 检查令牌是否无效
            if(this.isInvalidToken(jwt.getId()))
                return null;
            // 检查令牌是否已过期
            Date expiresAt = jwt.getExpiresAt();
            return new Date().after(expiresAt) ? null : jwt;
        } catch (JWTVerificationException e) {
            System.err.println("JWT verification failed: " + e.getMessage());
            // 如果验证异常，则返回null
            return null;
        }

    }


    /**
     * 将HTTP请求头中的Token转换为应用内使用的格式
     * 本函数专注于处理以"Bearer"开头的授权Token字符串，将其转换为应用内统一的Token格式
     *
     * @param headerToken HTTP请求头中的Token字符串，预期以"Bearer "开头
     * @return 转换后的Token字符串，如果不满足预期格式则返回null
     */
    private String convertToken(String headerToken) {
        // 检查输入的Token是否为空或是否以"Bearer"开头，如果不满足条件，则返回null
        if (headerToken == null || !headerToken.startsWith("Bearer ")) {
            return null;
        }
        // 从Token字符串中移除"Bearer"部分，仅返回实际的Token值
        return headerToken.substring(7);
    }

    /**
     * 将解码后的JWT转换为用户详细信息
     * 此方法用于将JWT中包含的用户信息转换为应用中的用户详细信息对象
     * 它从JWT中提取用户名和权限，并构建一个UserDetails对象，用于在应用中表示用户
     *
     * @param jwt 解码后的JWT，包含用户的相关信息
     * @return UserDetails对象，表示应用中的用户详细信息
     */
    public UserDetails toUserDetails(DecodedJWT jwt) {
        // 获取JWT中的所有声明
        Map<String, Claim> claims = jwt.getClaims();
        // 构建用户详细信息对象
        // 使用JWT中的"name"声明作为用户名
        // 密码在UserDetails对象中被屏蔽，不显示原始值，用"********"代替
        // 使用JWT中的"authorities"声明作为用户的权限数组
        return User
                .withUsername(claims.get("name").asString())
                .password("********")
                .authorities(claims.get("authorities").asArray(String.class))
                .build();
    }

    /**
     * 将DecodedJWT对象转换为ID整数
     * 此方法用于从JWT中的声明获取特定用户的ID该ID是通过JWT传递的，通常用于标识用户或实体
     *
     * @param jwt 解码后的JWT对象，包含一组声明
     * @return 返回从JWT声明中提取的用户或实体的ID
     */
    public Integer toId(DecodedJWT jwt) {
        // 获取JWT中的所有声明
        Map<String, Claim> claims = jwt.getClaims();
        // 从声明中获取"id"声明，并将其值转换为整数
        return claims.get("id").asInt();
    }

    /**
     * 计算过期时间
     * <p>
     * 该方法用于根据当前时间计算出指定天数后的过期时间它通过获取当前系统时间并加上过期天数（以小时为单位）
     * 来计算过期时间这个方法中并没有直接使用天数作为单位进行计算，而是将天数转换为小时数，这是因为Calendar类
     * 的add方法以小时为单位进行时间运算更加直观和灵活
     * @return Date 返回计算出的过期时间
     */
    public Date expireTime() {
        // 获取当前系统时间
        Calendar calendar = Calendar.getInstance();
        // 在当前时间基础上增加过期天数，这里将天数转换为小时数，因为Calendar类以小时为单位进行时间运算
        calendar.add(Calendar.HOUR, expire * 24);
        // 返回计算出的过期时间
        return calendar.getTime();
    }
}
